
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
// 
//   http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

/**
 * AUTO-GENERATED FILE. DO NOT MODIFY.
 */

// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
// 
//   http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.
import { __extends } from 'tslib'; // FIXME step not support polar

import * as zrUtil from 'zrender/lib/core/util.js';
import SymbolDraw from '../helper/SymbolDraw.js';
import SymbolClz from '../helper/Symbol.js';
import lineAnimationDiff from './lineAnimationDiff.js';
import * as graphic from '../../util/graphic.js';
import * as modelUtil from '../../util/model.js';
import { ECPolyline, ECPolygon } from './poly.js';
import ChartView from '../../view/Chart.js';
import { prepareDataCoordInfo, getStackedOnPoint } from './helper.js';
import { createGridClipPath, createPolarClipPath } from '../helper/createClipPathFromCoordSys.js';
import { isCoordinateSystemType } from '../../coord/CoordinateSystem.js';
import { setStatesStylesFromModel, setStatesFlag, toggleHoverEmphasis, SPECIAL_STATES } from '../../util/states.js';
import { setLabelStyle, getLabelStatesModels, labelInner } from '../../label/labelStyle.js';
import { getDefaultLabel, getDefaultInterpolatedLabel } from '../helper/labelHelper.js';
import { getECData } from '../../util/innerStore.js';
import { createFloat32Array } from '../../util/vendor.js';
import { convertToColorString } from '../../util/format.js';
import { lerp } from 'zrender/lib/tool/color.js';

function isPointsSame(points1, points2) {
	if (points1.length !== points2.length) {
		return;
	}

	for (var i = 0; i < points1.length; i++) {
		if (points1[i] !== points2[i]) {
			return;
		}
	}

	return true;
}

function bboxFromPoints(points) {
	var minX = Infinity;
	var minY = Infinity;
	var maxX = -Infinity;
	var maxY = -Infinity;

	for (var i = 0; i < points.length;) {
		var x = points[i++];
		var y = points[i++];

		if (!isNaN(x)) {
			minX = Math.min(x, minX);
			maxX = Math.max(x, maxX);
		}

		if (!isNaN(y)) {
			minY = Math.min(y, minY);
			maxY = Math.max(y, maxY);
		}
	}

	return [[minX, minY], [maxX, maxY]];
}

function getBoundingDiff(points1, points2) {
	var _a = bboxFromPoints(points1),
		min1 = _a[0],
		max1 = _a[1];

	var _b = bboxFromPoints(points2),
		min2 = _b[0],
		max2 = _b[1]; // Get a max value from each corner of two boundings.

	return Math.max(Math.abs(min1[0] - min2[0]), Math.abs(min1[1] - min2[1]), Math.abs(max1[0] - max2[0]), Math.abs(max1[1] - max2[1]));
}

function getSmooth(smooth) {
	return zrUtil.isNumber(smooth) ? smooth : smooth ? 0.5 : 0;
}

function getStackedOnPoints(coordSys, data, dataCoordInfo) {
	if (!dataCoordInfo.valueDim) {
		return [];
	}

	var len = data.count();
	var points = createFloat32Array(len * 2);

	for (var idx = 0; idx < len; idx++) {
		var pt = getStackedOnPoint(dataCoordInfo, coordSys, data, idx);
		points[idx * 2] = pt[0];
		points[idx * 2 + 1] = pt[1];
	}

	return points;
}

function turnPointsIntoStep(points, coordSys, stepTurnAt, connectNulls) {
	var baseAxis = coordSys.getBaseAxis();
	var baseIndex = baseAxis.dim === 'x' || baseAxis.dim === 'radius' ? 0 : 1;
	var stepPoints = [];
	var i = 0;
	var stepPt = [];
	var pt = [];
	var nextPt = [];
	var filteredPoints = [];

	if (connectNulls) {
		for (i = 0; i < points.length; i += 2) {
			if (!isNaN(points[i]) && !isNaN(points[i + 1])) {
				filteredPoints.push(points[i], points[i + 1]);
			}
		}

		points = filteredPoints;
	}

	for (i = 0; i < points.length - 2; i += 2) {
		nextPt[0] = points[i + 2];
		nextPt[1] = points[i + 3];
		pt[0] = points[i];
		pt[1] = points[i + 1];
		stepPoints.push(pt[0], pt[1]);

		switch (stepTurnAt) {
		case 'end':
			stepPt[baseIndex] = nextPt[baseIndex];
			stepPt[1 - baseIndex] = pt[1 - baseIndex];
			stepPoints.push(stepPt[0], stepPt[1]);
			break;

		case 'middle':
			var middle = (pt[baseIndex] + nextPt[baseIndex]) / 2;
			var stepPt2 = [];
			stepPt[baseIndex] = stepPt2[baseIndex] = middle;
			stepPt[1 - baseIndex] = pt[1 - baseIndex];
			stepPt2[1 - baseIndex] = nextPt[1 - baseIndex];
			stepPoints.push(stepPt[0], stepPt[1]);
			stepPoints.push(stepPt2[0], stepPt2[1]);
			break;

		default:
			// default is start
			stepPt[baseIndex] = pt[baseIndex];
			stepPt[1 - baseIndex] = nextPt[1 - baseIndex];
			stepPoints.push(stepPt[0], stepPt[1]);
		}
	} // Last points

	stepPoints.push(points[i++], points[i++]);
	return stepPoints;
}
/**
 * Clip color stops to edge. Avoid creating too large gradients.
 * Which may lead to blurry when GPU acceleration is enabled. See #15680
 *
 * The stops has been sorted from small to large.
 */

function clipColorStops(colorStops, maxSize) {
	var newColorStops = [];
	var len = colorStops.length; // coord will always < 0 in prevOutOfRangeColorStop.

	var prevOutOfRangeColorStop;
	var prevInRangeColorStop;

	function lerpStop(stop0, stop1, clippedCoord) {
		var coord0 = stop0.coord;
		var p = (clippedCoord - coord0) / (stop1.coord - coord0);
		var color = lerp(p, [stop0.color, stop1.color]);
		return {
			coord: clippedCoord,
			color: color
		};
	}

	for (var i = 0; i < len; i++) {
		var stop_1 = colorStops[i];
		var coord = stop_1.coord;

		if (coord < 0) {
			prevOutOfRangeColorStop = stop_1;
		} else if (coord > maxSize) {
			if (prevInRangeColorStop) {
				newColorStops.push(lerpStop(prevInRangeColorStop, stop_1, maxSize));
			} else if (prevOutOfRangeColorStop) {
				// If there are two stops and coord range is between these two stops
				newColorStops.push(lerpStop(prevOutOfRangeColorStop, stop_1, 0), lerpStop(prevOutOfRangeColorStop, stop_1, maxSize));
			} // All following stop will be out of range. So just ignore them.

			break;
		} else {
			if (prevOutOfRangeColorStop) {
				newColorStops.push(lerpStop(prevOutOfRangeColorStop, stop_1, 0)); // Reset

				prevOutOfRangeColorStop = null;
			}

			newColorStops.push(stop_1);
			prevInRangeColorStop = stop_1;
		}
	}

	return newColorStops;
}

function getVisualGradient(data, coordSys, api) {
	var visualMetaList = data.getVisual('visualMeta');

	if (!visualMetaList || !visualMetaList.length || !data.count()) {
		// When data.count() is 0, gradient range can not be calculated.
		return;
	}

	if (coordSys.type !== 'cartesian2d') {
		if (process.env.NODE_ENV !== 'production') {
			console.warn('Visual map on line style is only supported on cartesian2d.');
		}

		return;
	}

	var coordDim;
	var visualMeta;

	for (var i = visualMetaList.length - 1; i >= 0; i--) {
		var dimInfo = data.getDimensionInfo(visualMetaList[i].dimension);
		coordDim = dimInfo && dimInfo.coordDim; // Can only be x or y

		if (coordDim === 'x' || coordDim === 'y') {
			visualMeta = visualMetaList[i];
			break;
		}
	}

	if (!visualMeta) {
		if (process.env.NODE_ENV !== 'production') {
			console.warn('Visual map on line style only support x or y dimension.');
		}

		return;
	} // If the area to be rendered is bigger than area defined by LinearGradient,
	// the canvas spec prescribes that the color of the first stop and the last
	// stop should be used. But if two stops are added at offset 0, in effect
	// browsers use the color of the second stop to render area outside
	// LinearGradient. So we can only infinitesimally extend area defined in
	// LinearGradient to render `outerColors`.

	var axis = coordSys.getAxis(coordDim); // dataToCoord mapping may not be linear, but must be monotonic.

	var colorStops = zrUtil.map(visualMeta.stops, function (stop) {
		// offset will be calculated later.
		return {
			coord: axis.toGlobalCoord(axis.dataToCoord(stop.value)),
			color: stop.color
		};
	});
	var stopLen = colorStops.length;
	var outerColors = visualMeta.outerColors.slice();

	if (stopLen && colorStops[0].coord > colorStops[stopLen - 1].coord) {
		colorStops.reverse();
		outerColors.reverse();
	}

	var colorStopsInRange = clipColorStops(colorStops, coordDim === 'x' ? api.getWidth() : api.getHeight());
	var inRangeStopLen = colorStopsInRange.length;

	if (!inRangeStopLen && stopLen) {
		// All stops are out of range. All will be the same color.
		return colorStops[0].coord < 0 ? outerColors[1] ? outerColors[1] : colorStops[stopLen - 1].color : outerColors[0] ? outerColors[0] : colorStops[0].color;
	}

	var tinyExtent = 10; // Arbitrary value: 10px

	var minCoord = colorStopsInRange[0].coord - tinyExtent;
	var maxCoord = colorStopsInRange[inRangeStopLen - 1].coord + tinyExtent;
	var coordSpan = maxCoord - minCoord;

	if (coordSpan < 1e-3) {
		return 'transparent';
	}

	zrUtil.each(colorStopsInRange, function (stop) {
		stop.offset = (stop.coord - minCoord) / coordSpan;
	});
	colorStopsInRange.push({
		// NOTE: inRangeStopLen may still be 0 if stoplen is zero.
		offset: inRangeStopLen ? colorStopsInRange[inRangeStopLen - 1].offset : 0.5,
		color: outerColors[1] || 'transparent'
	});
	colorStopsInRange.unshift({
		offset: inRangeStopLen ? colorStopsInRange[0].offset : 0.5,
		color: outerColors[0] || 'transparent'
	});
	var gradient = new graphic.LinearGradient(0, 0, 0, 0, colorStopsInRange, true);
	gradient[coordDim] = minCoord;
	gradient[coordDim + '2'] = maxCoord;
	return gradient;
}

function getIsIgnoreFunc(seriesModel, data, coordSys) {
	var showAllSymbol = seriesModel.get('showAllSymbol');
	var isAuto = showAllSymbol === 'auto';

	if (showAllSymbol && !isAuto) {
		return;
	}

	var categoryAxis = coordSys.getAxesByScale('ordinal')[0];

	if (!categoryAxis) {
		return;
	} // Note that category label interval strategy might bring some weird effect
	// in some scenario: users may wonder why some of the symbols are not
	// displayed. So we show all symbols as possible as we can.

	if (isAuto // Simplify the logic, do not determine label overlap here.
  && canShowAllSymbolForCategory(categoryAxis, data)) {
		return;
	} // Otherwise follow the label interval strategy on category axis.

	var categoryDataDim = data.mapDimension(categoryAxis.dim);
	var labelMap = {};
	zrUtil.each(categoryAxis.getViewLabels(), function (labelItem) {
		var ordinalNumber = categoryAxis.scale.getRawOrdinalNumber(labelItem.tickValue);
		labelMap[ordinalNumber] = 1;
	});
	return function (dataIndex) {
		return !labelMap.hasOwnProperty(data.get(categoryDataDim, dataIndex));
	};
}

function canShowAllSymbolForCategory(categoryAxis, data) {
	// In most cases, line is monotonous on category axis, and the label size
	// is close with each other. So we check the symbol size and some of the
	// label size alone with the category axis to estimate whether all symbol
	// can be shown without overlap.
	var axisExtent = categoryAxis.getExtent();
	var availSize = Math.abs(axisExtent[1] - axisExtent[0]) / categoryAxis.scale.count();
	isNaN(availSize) && (availSize = 0); // 0/0 is NaN.
	// Sampling some points, max 5.

	var dataLen = data.count();
	var step = Math.max(1, Math.round(dataLen / 5));

	for (var dataIndex = 0; dataIndex < dataLen; dataIndex += step) {
		if (SymbolClz.getSymbolSize(data, dataIndex // Only for cartesian, where `isHorizontal` exists.
		)[categoryAxis.isHorizontal() ? 1 : 0] // Empirical number
    * 1.5 > availSize) {
			return false;
		}
	}

	return true;
}

function isPointNull(x, y) {
	return isNaN(x) || isNaN(y);
}

function getLastIndexNotNull(points) {
	var len = points.length / 2;

	for (; len > 0; len--) {
		if (!isPointNull(points[len * 2 - 2], points[len * 2 - 1])) {
			break;
		}
	}

	return len - 1;
}

function getPointAtIndex(points, idx) {
	return [points[idx * 2], points[idx * 2 + 1]];
}

function getIndexRange(points, xOrY, dim) {
	var len = points.length / 2;
	var dimIdx = dim === 'x' ? 0 : 1;
	var a;
	var b;
	var prevIndex = 0;
	var nextIndex = -1;

	for (var i = 0; i < len; i++) {
		b = points[i * 2 + dimIdx];

		if (isNaN(b) || isNaN(points[i * 2 + 1 - dimIdx])) {
			continue;
		}

		if (i === 0) {
			a = b;
			continue;
		}

		if (a <= xOrY && b >= xOrY || a >= xOrY && b <= xOrY) {
			nextIndex = i;
			break;
		}

		prevIndex = i;
		a = b;
	}

	return {
		range: [prevIndex, nextIndex],
		t: (xOrY - a) / (b - a)
	};
}

function anyStateShowEndLabel(seriesModel) {
	if (seriesModel.get(['endLabel', 'show'])) {
		return true;
	}

	for (var i = 0; i < SPECIAL_STATES.length; i++) {
		if (seriesModel.get([SPECIAL_STATES[i], 'endLabel', 'show'])) {
			return true;
		}
	}

	return false;
}

function createLineClipPath(lineView, coordSys, hasAnimation, seriesModel) {
	if (isCoordinateSystemType(coordSys, 'cartesian2d')) {
		var endLabelModel_1 = seriesModel.getModel('endLabel');
		var valueAnimation_1 = endLabelModel_1.get('valueAnimation');
		var data_1 = seriesModel.getData();
		var labelAnimationRecord_1 = {
			lastFrameIndex: 0
		};
		var during = anyStateShowEndLabel(seriesModel) ? function (percent, clipRect) {
			lineView._endLabelOnDuring(percent, clipRect, data_1, labelAnimationRecord_1, valueAnimation_1, endLabelModel_1, coordSys);
		} : null;
		var isHorizontal = coordSys.getBaseAxis().isHorizontal();
		var clipPath = createGridClipPath(coordSys, hasAnimation, seriesModel, function () {
			var endLabel = lineView._endLabel;

			if (endLabel && hasAnimation) {
				if (labelAnimationRecord_1.originalX != null) {
					endLabel.attr({
						x: labelAnimationRecord_1.originalX,
						y: labelAnimationRecord_1.originalY
					});
				}
			}
		}, during); // Expand clip shape to avoid clipping when line value exceeds axis

		if (!seriesModel.get('clip', true)) {
			var rectShape = clipPath.shape;
			var expandSize = Math.max(rectShape.width, rectShape.height);

			if (isHorizontal) {
				rectShape.y -= expandSize;
				rectShape.height += expandSize * 2;
			} else {
				rectShape.x -= expandSize;
				rectShape.width += expandSize * 2;
			}
		} // Set to the final frame. To make sure label layout is right.

		if (during) {
			during(1, clipPath);
		}

		return clipPath;
	} else {
		if (process.env.NODE_ENV !== 'production') {
			if (seriesModel.get(['endLabel', 'show'])) {
				console.warn('endLabel is not supported for lines in polar systems.');
			}
		}

		return createPolarClipPath(coordSys, hasAnimation, seriesModel);
	}
}

function getEndLabelStateSpecified(endLabelModel, coordSys) {
	var baseAxis = coordSys.getBaseAxis();
	var isHorizontal = baseAxis.isHorizontal();
	var isBaseInversed = baseAxis.inverse;
	var align = isHorizontal ? isBaseInversed ? 'right' : 'left' : 'center';
	var verticalAlign = isHorizontal ? 'middle' : isBaseInversed ? 'top' : 'bottom';
	return {
		normal: {
			align: endLabelModel.get('align') || align,
			verticalAlign: endLabelModel.get('verticalAlign') || verticalAlign
		}
	};
}

var LineView =
/** @class */
function (_super) {
	__extends(LineView, _super);

	function LineView() {
		return _super !== null && _super.apply(this, arguments) || this;
	}

	LineView.prototype.init = function () {
		var lineGroup = new graphic.Group();
		var symbolDraw = new SymbolDraw();
		this.group.add(symbolDraw.group);
		this._symbolDraw = symbolDraw;
		this._lineGroup = lineGroup;
	};

	LineView.prototype.render = function (seriesModel, ecModel, api) {
		var _this = this;

		var coordSys = seriesModel.coordinateSystem;
		var group = this.group;
		var data = seriesModel.getData();
		var lineStyleModel = seriesModel.getModel('lineStyle');
		var areaStyleModel = seriesModel.getModel('areaStyle');
		var points = data.getLayout('points') || [];
		var isCoordSysPolar = coordSys.type === 'polar';
		var prevCoordSys = this._coordSys;
		var symbolDraw = this._symbolDraw;
		var polyline = this._polyline;
		var polygon = this._polygon;
		var lineGroup = this._lineGroup;
		var hasAnimation = !ecModel.ssr && seriesModel.isAnimationEnabled();
		var isAreaChart = !areaStyleModel.isEmpty();
		var valueOrigin = areaStyleModel.get('origin');
		var dataCoordInfo = prepareDataCoordInfo(coordSys, data, valueOrigin);
		var stackedOnPoints = isAreaChart && getStackedOnPoints(coordSys, data, dataCoordInfo);
		var showSymbol = seriesModel.get('showSymbol');
		var connectNulls = seriesModel.get('connectNulls');
		var isIgnoreFunc = showSymbol && !isCoordSysPolar && getIsIgnoreFunc(seriesModel, data, coordSys); // Remove temporary symbols

		var oldData = this._data;
		oldData && oldData.eachItemGraphicEl(function (el, idx) {
			if (el.__temp) {
				group.remove(el);
				oldData.setItemGraphicEl(idx, null);
			}
		}); // Remove previous created symbols if showSymbol changed to false

		if (!showSymbol) {
			symbolDraw.remove();
		}

		group.add(lineGroup); // FIXME step not support polar

		var step = !isCoordSysPolar ? seriesModel.get('step') : false;
		var clipShapeForSymbol;

		if (coordSys && coordSys.getArea && seriesModel.get('clip', true)) {
			clipShapeForSymbol = coordSys.getArea(); // Avoid float number rounding error for symbol on the edge of axis extent.
			// See #7913 and `test/dataZoom-clip.html`.

			if (clipShapeForSymbol.width != null) {
				clipShapeForSymbol.x -= 0.1;
				clipShapeForSymbol.y -= 0.1;
				clipShapeForSymbol.width += 0.2;
				clipShapeForSymbol.height += 0.2;
			} else if (clipShapeForSymbol.r0) {
				clipShapeForSymbol.r0 -= 0.5;
				clipShapeForSymbol.r += 0.5;
			}
		}

		this._clipShapeForSymbol = clipShapeForSymbol;
		var visualColor = getVisualGradient(data, coordSys, api) || data.getVisual('style')[data.getVisual('drawType')]; // Initialization animation or coordinate system changed

		if (!(polyline && prevCoordSys.type === coordSys.type && step === this._step)) {
			showSymbol && symbolDraw.updateData(data, {
				isIgnore: isIgnoreFunc,
				clipShape: clipShapeForSymbol,
				disableAnimation: true,
				getSymbolPoint: function (idx) {
					return [points[idx * 2], points[idx * 2 + 1]];
				}
			});
			hasAnimation && this._initSymbolLabelAnimation(data, coordSys, clipShapeForSymbol);

			if (step) {
				// TODO If stacked series is not step
				points = turnPointsIntoStep(points, coordSys, step, connectNulls);

				if (stackedOnPoints) {
					stackedOnPoints = turnPointsIntoStep(stackedOnPoints, coordSys, step, connectNulls);
				}
			}

			polyline = this._newPolyline(points);

			if (isAreaChart) {
				polygon = this._newPolygon(points, stackedOnPoints);
			} // If areaStyle is removed
			else if (polygon) {
				lineGroup.remove(polygon);
				polygon = this._polygon = null;
			} // NOTE: Must update _endLabel before setClipPath.

			if (!isCoordSysPolar) {
				this._initOrUpdateEndLabel(seriesModel, coordSys, convertToColorString(visualColor));
			}

			lineGroup.setClipPath(createLineClipPath(this, coordSys, true, seriesModel));
		} else {
			if (isAreaChart && !polygon) {
				// If areaStyle is added
				polygon = this._newPolygon(points, stackedOnPoints);
			} else if (polygon && !isAreaChart) {
				// If areaStyle is removed
				lineGroup.remove(polygon);
				polygon = this._polygon = null;
			} // NOTE: Must update _endLabel before setClipPath.

			if (!isCoordSysPolar) {
				this._initOrUpdateEndLabel(seriesModel, coordSys, convertToColorString(visualColor));
			} // Update clipPath

			var oldClipPath = lineGroup.getClipPath();

			if (oldClipPath) {
				var newClipPath = createLineClipPath(this, coordSys, false, seriesModel);
				graphic.initProps(oldClipPath, {
					shape: newClipPath.shape
				}, seriesModel);
			} else {
				lineGroup.setClipPath(createLineClipPath(this, coordSys, true, seriesModel));
			} // Always update, or it is wrong in the case turning on legend
			// because points are not changed.

			showSymbol && symbolDraw.updateData(data, {
				isIgnore: isIgnoreFunc,
				clipShape: clipShapeForSymbol,
				disableAnimation: true,
				getSymbolPoint: function (idx) {
					return [points[idx * 2], points[idx * 2 + 1]];
				}
			}); // In the case data zoom triggered refreshing frequently
			// Data may not change if line has a category axis. So it should animate nothing.

			if (!isPointsSame(this._stackedOnPoints, stackedOnPoints) || !isPointsSame(this._points, points)) {
				if (hasAnimation) {
					this._doUpdateAnimation(data, stackedOnPoints, coordSys, api, step, valueOrigin, connectNulls);
				} else {
					// Not do it in update with animation
					if (step) {
						// TODO If stacked series is not step
						points = turnPointsIntoStep(points, coordSys, step, connectNulls);

						if (stackedOnPoints) {
							stackedOnPoints = turnPointsIntoStep(stackedOnPoints, coordSys, step, connectNulls);
						}
					}

					polyline.setShape({
						points: points
					});
					polygon && polygon.setShape({
						points: points,
						stackedOnPoints: stackedOnPoints
					});
				}
			}
		}

		var emphasisModel = seriesModel.getModel('emphasis');
		var focus = emphasisModel.get('focus');
		var blurScope = emphasisModel.get('blurScope');
		var emphasisDisabled = emphasisModel.get('disabled');
		polyline.useStyle(zrUtil.defaults( // Use color in lineStyle first
			lineStyleModel.getLineStyle(), {
				fill: 'none',
				stroke: visualColor,
				lineJoin: 'bevel'
			}));
		setStatesStylesFromModel(polyline, seriesModel, 'lineStyle');

		if (polyline.style.lineWidth > 0 && seriesModel.get(['emphasis', 'lineStyle', 'width']) === 'bolder') {
			var emphasisLineStyle = polyline.getState('emphasis').style;
			emphasisLineStyle.lineWidth = +polyline.style.lineWidth + 1;
		} // Needs seriesIndex for focus

		getECData(polyline).seriesIndex = seriesModel.seriesIndex;
		toggleHoverEmphasis(polyline, focus, blurScope, emphasisDisabled);
		var smooth = getSmooth(seriesModel.get('smooth'));
		var smoothMonotone = seriesModel.get('smoothMonotone');
		polyline.setShape({
			smooth: smooth,
			smoothMonotone: smoothMonotone,
			connectNulls: connectNulls
		});

		if (polygon) {
			var stackedOnSeries = data.getCalculationInfo('stackedOnSeries');
			var stackedOnSmooth = 0;
			polygon.useStyle(zrUtil.defaults(areaStyleModel.getAreaStyle(), {
				fill: visualColor,
				opacity: 0.7,
				lineJoin: 'bevel',
				decal: data.getVisual('style').decal
			}));

			if (stackedOnSeries) {
				stackedOnSmooth = getSmooth(stackedOnSeries.get('smooth'));
			}

			polygon.setShape({
				smooth: smooth,
				stackedOnSmooth: stackedOnSmooth,
				smoothMonotone: smoothMonotone,
				connectNulls: connectNulls
			});
			setStatesStylesFromModel(polygon, seriesModel, 'areaStyle'); // Needs seriesIndex for focus

			getECData(polygon).seriesIndex = seriesModel.seriesIndex;
			toggleHoverEmphasis(polygon, focus, blurScope, emphasisDisabled);
		}

		var changePolyState = function (toState) {
			_this._changePolyState(toState);
		};

		data.eachItemGraphicEl(function (el) {
			// Switch polyline / polygon state if element changed its state.
			el && (el.onHoverStateChange = changePolyState);
		});
		this._polyline.onHoverStateChange = changePolyState;
		this._data = data; // Save the coordinate system for transition animation when data changed

		this._coordSys = coordSys;
		this._stackedOnPoints = stackedOnPoints;
		this._points = points;
		this._step = step;
		this._valueOrigin = valueOrigin;

		if (seriesModel.get('triggerLineEvent')) {
			this.packEventData(seriesModel, polyline);
			polygon && this.packEventData(seriesModel, polygon);
		}
	};

	LineView.prototype.packEventData = function (seriesModel, el) {
		getECData(el).eventData = {
			componentType: 'series',
			componentSubType: 'line',
			componentIndex: seriesModel.componentIndex,
			seriesIndex: seriesModel.seriesIndex,
			seriesName: seriesModel.name,
			seriesType: 'line'
		};
	};

	LineView.prototype.highlight = function (seriesModel, ecModel, api, payload) {
		var data = seriesModel.getData();
		var dataIndex = modelUtil.queryDataIndex(data, payload);

		this._changePolyState('emphasis');

		if (!(dataIndex instanceof Array) && dataIndex != null && dataIndex >= 0) {
			var points = data.getLayout('points');
			var symbol = data.getItemGraphicEl(dataIndex);

			if (!symbol) {
				// Create a temporary symbol if it is not exists
				var x = points[dataIndex * 2];
				var y = points[dataIndex * 2 + 1];

				if (isNaN(x) || isNaN(y)) {
					// Null data
					return;
				} // fix #11360: shouldn't draw symbol outside clipShapeForSymbol

				if (this._clipShapeForSymbol && !this._clipShapeForSymbol.contain(x, y)) {
					return;
				}

				var zlevel = seriesModel.get('zlevel') || 0;
				var z = seriesModel.get('z') || 0;
				symbol = new SymbolClz(data, dataIndex);
				symbol.x = x;
				symbol.y = y;
				symbol.setZ(zlevel, z); // ensure label text of the temporary symbol is in front of line and area polygon

				var symbolLabel = symbol.getSymbolPath().getTextContent();

				if (symbolLabel) {
					symbolLabel.zlevel = zlevel;
					symbolLabel.z = z;
					symbolLabel.z2 = this._polyline.z2 + 1;
				}

				symbol.__temp = true;
				data.setItemGraphicEl(dataIndex, symbol); // Stop scale animation

				symbol.stopSymbolAnimation(true);
				this.group.add(symbol);
			}

			symbol.highlight();
		} else {
			// Highlight whole series
			ChartView.prototype.highlight.call(this, seriesModel, ecModel, api, payload);
		}
	};

	LineView.prototype.downplay = function (seriesModel, ecModel, api, payload) {
		var data = seriesModel.getData();
		var dataIndex = modelUtil.queryDataIndex(data, payload);

		this._changePolyState('normal');

		if (dataIndex != null && dataIndex >= 0) {
			var symbol = data.getItemGraphicEl(dataIndex);

			if (symbol) {
				if (symbol.__temp) {
					data.setItemGraphicEl(dataIndex, null);
					this.group.remove(symbol);
				} else {
					symbol.downplay();
				}
			}
		} else {
			// FIXME
			// can not downplay completely.
			// Downplay whole series
			ChartView.prototype.downplay.call(this, seriesModel, ecModel, api, payload);
		}
	};

	LineView.prototype._changePolyState = function (toState) {
		var polygon = this._polygon;
		setStatesFlag(this._polyline, toState);
		polygon && setStatesFlag(polygon, toState);
	};

	LineView.prototype._newPolyline = function (points) {
		var polyline = this._polyline; // Remove previous created polyline

		if (polyline) {
			this._lineGroup.remove(polyline);
		}

		polyline = new ECPolyline({
			shape: {
				points: points
			},
			segmentIgnoreThreshold: 2,
			z2: 10
		});

		this._lineGroup.add(polyline);

		this._polyline = polyline;
		return polyline;
	};

	LineView.prototype._newPolygon = function (points, stackedOnPoints) {
		var polygon = this._polygon; // Remove previous created polygon

		if (polygon) {
			this._lineGroup.remove(polygon);
		}

		polygon = new ECPolygon({
			shape: {
				points: points,
				stackedOnPoints: stackedOnPoints
			},
			segmentIgnoreThreshold: 2
		});

		this._lineGroup.add(polygon);

		this._polygon = polygon;
		return polygon;
	};

	LineView.prototype._initSymbolLabelAnimation = function (data, coordSys, clipShape) {
		var isHorizontalOrRadial;
		var isCoordSysPolar;
		var baseAxis = coordSys.getBaseAxis();
		var isAxisInverse = baseAxis.inverse;

		if (coordSys.type === 'cartesian2d') {
			isHorizontalOrRadial = baseAxis.isHorizontal();
			isCoordSysPolar = false;
		} else if (coordSys.type === 'polar') {
			isHorizontalOrRadial = baseAxis.dim === 'angle';
			isCoordSysPolar = true;
		}

		var seriesModel = data.hostModel;
		var seriesDuration = seriesModel.get('animationDuration');

		if (zrUtil.isFunction(seriesDuration)) {
			seriesDuration = seriesDuration(null);
		}

		var seriesDelay = seriesModel.get('animationDelay') || 0;
		var seriesDelayValue = zrUtil.isFunction(seriesDelay) ? seriesDelay(null) : seriesDelay;
		data.eachItemGraphicEl(function (symbol, idx) {
			var el = symbol;

			if (el) {
				var point = [symbol.x, symbol.y];
				var start = void 0;
				var end = void 0;
				var current = void 0;

				if (clipShape) {
					if (isCoordSysPolar) {
						var polarClip = clipShape;
						var coord = coordSys.pointToCoord(point);

						if (isHorizontalOrRadial) {
							start = polarClip.startAngle;
							end = polarClip.endAngle;
							current = -coord[1] / 180 * Math.PI;
						} else {
							start = polarClip.r0;
							end = polarClip.r;
							current = coord[0];
						}
					} else {
						var gridClip = clipShape;

						if (isHorizontalOrRadial) {
							start = gridClip.x;
							end = gridClip.x + gridClip.width;
							current = symbol.x;
						} else {
							start = gridClip.y + gridClip.height;
							end = gridClip.y;
							current = symbol.y;
						}
					}
				}

				var ratio = end === start ? 0 : (current - start) / (end - start);

				if (isAxisInverse) {
					ratio = 1 - ratio;
				}

				var delay = zrUtil.isFunction(seriesDelay) ? seriesDelay(idx) : seriesDuration * ratio + seriesDelayValue;
				var symbolPath = el.getSymbolPath();
				var text = symbolPath.getTextContent();
				el.attr({
					scaleX: 0,
					scaleY: 0
				});
				el.animateTo({
					scaleX: 1,
					scaleY: 1
				}, {
					duration: 200,
					setToFinal: true,
					delay: delay
				});

				if (text) {
					text.animateFrom({
						style: {
							opacity: 0
						}
					}, {
						duration: 300,
						delay: delay
					});
				}

				symbolPath.disableLabelAnimation = true;
			}
		});
	};

	LineView.prototype._initOrUpdateEndLabel = function (seriesModel, coordSys, inheritColor) {
		var endLabelModel = seriesModel.getModel('endLabel');

		if (anyStateShowEndLabel(seriesModel)) {
			var data_2 = seriesModel.getData();
			var polyline = this._polyline; // series may be filtered.

			var points = data_2.getLayout('points');

			if (!points) {
				polyline.removeTextContent();
				this._endLabel = null;
				return;
			}

			var endLabel = this._endLabel;

			if (!endLabel) {
				endLabel = this._endLabel = new graphic.Text({
					z2: 200 // should be higher than item symbol

				});
				endLabel.ignoreClip = true;
				polyline.setTextContent(this._endLabel);
				polyline.disableLabelAnimation = true;
			} // Find last non-NaN data to display data

			var dataIndex = getLastIndexNotNull(points);

			if (dataIndex >= 0) {
				setLabelStyle(polyline, getLabelStatesModels(seriesModel, 'endLabel'), {
					inheritColor: inheritColor,
					labelFetcher: seriesModel,
					labelDataIndex: dataIndex,
					defaultText: function (dataIndex, opt, interpolatedValue) {
						return interpolatedValue != null ? getDefaultInterpolatedLabel(data_2, interpolatedValue) : getDefaultLabel(data_2, dataIndex);
					},
					enableTextSetter: true
				}, getEndLabelStateSpecified(endLabelModel, coordSys));
				polyline.textConfig.position = null;
			}
		} else if (this._endLabel) {
			this._polyline.removeTextContent();

			this._endLabel = null;
		}
	};

	LineView.prototype._endLabelOnDuring = function (percent, clipRect, data, animationRecord, valueAnimation, endLabelModel, coordSys) {
		var endLabel = this._endLabel;
		var polyline = this._polyline;

		if (endLabel) {
			// NOTE: Don't remove percent < 1. percent === 1 means the first frame during render.
			// The label is not prepared at this time.
			if (percent < 1 && animationRecord.originalX == null) {
				animationRecord.originalX = endLabel.x;
				animationRecord.originalY = endLabel.y;
			}

			var points = data.getLayout('points');
			var seriesModel = data.hostModel;
			var connectNulls = seriesModel.get('connectNulls');
			var precision = endLabelModel.get('precision');
			var distance = endLabelModel.get('distance') || 0;
			var baseAxis = coordSys.getBaseAxis();
			var isHorizontal = baseAxis.isHorizontal();
			var isBaseInversed = baseAxis.inverse;
			var clipShape = clipRect.shape;
			var xOrY = isBaseInversed ? isHorizontal ? clipShape.x : clipShape.y + clipShape.height : isHorizontal ? clipShape.x + clipShape.width : clipShape.y;
			var distanceX = (isHorizontal ? distance : 0) * (isBaseInversed ? -1 : 1);
			var distanceY = (isHorizontal ? 0 : -distance) * (isBaseInversed ? -1 : 1);
			var dim = isHorizontal ? 'x' : 'y';
			var dataIndexRange = getIndexRange(points, xOrY, dim);
			var indices = dataIndexRange.range;
			var diff = indices[1] - indices[0];
			var value = void 0;

			if (diff >= 1) {
				// diff > 1 && connectNulls, which is on the null data.
				if (diff > 1 && !connectNulls) {
					var pt = getPointAtIndex(points, indices[0]);
					endLabel.attr({
						x: pt[0] + distanceX,
						y: pt[1] + distanceY
					});
					valueAnimation && (value = seriesModel.getRawValue(indices[0]));
				} else {
					var pt = polyline.getPointOn(xOrY, dim);
					pt && endLabel.attr({
						x: pt[0] + distanceX,
						y: pt[1] + distanceY
					});
					var startValue = seriesModel.getRawValue(indices[0]);
					var endValue = seriesModel.getRawValue(indices[1]);
					valueAnimation && (value = modelUtil.interpolateRawValues(data, precision, startValue, endValue, dataIndexRange.t));
				}

				animationRecord.lastFrameIndex = indices[0];
			} else {
				// If diff <= 0, which is the range is not found(Include NaN)
				// Choose the first point or last point.
				var idx = percent === 1 || animationRecord.lastFrameIndex > 0 ? indices[0] : 0;
				var pt = getPointAtIndex(points, idx);
				valueAnimation && (value = seriesModel.getRawValue(idx));
				endLabel.attr({
					x: pt[0] + distanceX,
					y: pt[1] + distanceY
				});
			}

			if (valueAnimation) {
				var inner = labelInner(endLabel);

				if (typeof inner.setLabelText === 'function') {
					inner.setLabelText(value);
				}
			}
		}
	};
	/**
   * @private
   */
	// FIXME Two value axis

	LineView.prototype._doUpdateAnimation = function (data, stackedOnPoints, coordSys, api, step, valueOrigin, connectNulls) {
		var polyline = this._polyline;
		var polygon = this._polygon;
		var seriesModel = data.hostModel;
		var diff = lineAnimationDiff(this._data, data, this._stackedOnPoints, stackedOnPoints, this._coordSys, coordSys, this._valueOrigin, valueOrigin);
		var current = diff.current;
		var stackedOnCurrent = diff.stackedOnCurrent;
		var next = diff.next;
		var stackedOnNext = diff.stackedOnNext;

		if (step) {
			// TODO If stacked series is not step
			current = turnPointsIntoStep(diff.current, coordSys, step, connectNulls);
			stackedOnCurrent = turnPointsIntoStep(diff.stackedOnCurrent, coordSys, step, connectNulls);
			next = turnPointsIntoStep(diff.next, coordSys, step, connectNulls);
			stackedOnNext = turnPointsIntoStep(diff.stackedOnNext, coordSys, step, connectNulls);
		} // Don't apply animation if diff is large.
		// For better result and avoid memory explosion problems like
		// https://github.com/apache/incubator-echarts/issues/12229

		if (getBoundingDiff(current, next) > 3000 || polygon && getBoundingDiff(stackedOnCurrent, stackedOnNext) > 3000) {
			polyline.stopAnimation();
			polyline.setShape({
				points: next
			});

			if (polygon) {
				polygon.stopAnimation();
				polygon.setShape({
					points: next,
					stackedOnPoints: stackedOnNext
				});
			}

			return;
		}

		polyline.shape.__points = diff.current;
		polyline.shape.points = current;
		var target = {
			shape: {
				points: next
			}
		}; // Also animate the original points.
		// If points reference is changed when turning into step line.

		if (diff.current !== current) {
			target.shape.__points = diff.next;
		} // Stop previous animation.

		polyline.stopAnimation();
		graphic.updateProps(polyline, target, seriesModel);

		if (polygon) {
			polygon.setShape({
				// Reuse the points with polyline.
				points: current,
				stackedOnPoints: stackedOnCurrent
			});
			polygon.stopAnimation();
			graphic.updateProps(polygon, {
				shape: {
					stackedOnPoints: stackedOnNext
				}
			}, seriesModel); // If use attr directly in updateProps.

			if (polyline.shape.points !== polygon.shape.points) {
				polygon.shape.points = polyline.shape.points;
			}
		}

		var updatedDataInfo = [];
		var diffStatus = diff.status;

		for (var i = 0; i < diffStatus.length; i++) {
			var cmd = diffStatus[i].cmd;

			if (cmd === '=') {
				var el = data.getItemGraphicEl(diffStatus[i].idx1);

				if (el) {
					updatedDataInfo.push({
						el: el,
						ptIdx: i // Index of points

					});
				}
			}
		}

		if (polyline.animators && polyline.animators.length) {
			polyline.animators[0].during(function () {
				polygon && polygon.dirtyShape();
				var points = polyline.shape.__points;

				for (var i = 0; i < updatedDataInfo.length; i++) {
					var el = updatedDataInfo[i].el;
					var offset = updatedDataInfo[i].ptIdx * 2;
					el.x = points[offset];
					el.y = points[offset + 1];
					el.markRedraw();
				}
			});
		}
	};

	LineView.prototype.remove = function (ecModel) {
		var group = this.group;
		var oldData = this._data;

		this._lineGroup.removeAll();

		this._symbolDraw.remove(true); // Remove temporary created elements when highlighting

		oldData && oldData.eachItemGraphicEl(function (el, idx) {
			if (el.__temp) {
				group.remove(el);
				oldData.setItemGraphicEl(idx, null);
			}
		});
		this._polyline = this._polygon = this._coordSys = this._points = this._stackedOnPoints = this._endLabel = this._data = null;
	};

	LineView.type = 'line';
	return LineView;
}(ChartView);

export default LineView;